# -*- coding: binary -*-
require 'rex/proto/kerberos'

module Msf
  class Exploit
    class Remote
      module Kerberos
        module Client
          require 'msf/core/exploit/kerberos/client/base'
          require 'msf/core/exploit/kerberos/client/as_request'
          require 'msf/core/exploit/kerberos/client/as_response'
          require 'msf/core/exploit/kerberos/client/tgs_request'
          require 'msf/core/exploit/kerberos/client/tgs_response'
          require 'msf/core/exploit/kerberos/client/pac'
          require 'msf/core/exploit/kerberos/client/cache_credential'

          include Msf::Exploit::Remote::Kerberos::Client::Base
          include Msf::Exploit::Remote::Kerberos::Client::AsRequest
          include Msf::Exploit::Remote::Kerberos::Client::AsResponse
          include Msf::Exploit::Remote::Kerberos::Client::TgsRequest
          include Msf::Exploit::Remote::Kerberos::Client::TgsResponse
          include Msf::Exploit::Remote::Kerberos::Client::Pac
          include Msf::Exploit::Remote::Kerberos::Client::CacheCredential

          # @!attribute client
          #   @return [Rex::Proto::Kerberos::Client] The kerberos client
          attr_accessor :client

          def initialize(info = {})
            super

            register_options(
              [
                Opt::RHOST,
                Opt::RPORT(88),
                OptInt.new('Timeout', [true, 'The TCP timeout to establish connection and read data', 10])
              ], self.class
            )
          end

          # Returns the target host
          #
          # @return [String]
          def rhost
            datastore['RHOST']
          end

          # Returns the remote port
          #
          # @return [Integer]
          def rport
            datastore['RPORT']
          end

          # Returns the TCP timeout
          #
          # @return [Integer]
          def timeout
            datastore['Timeout']
          end

          # Returns the kdc peer
          #
          # @return [String]
          def peer
            "#{rhost}:#{rport}"
          end

          # Creates a kerberos connection
          #
          # @param opts [Hash{Symbol => <String, Integer>}]
          # @option opts [String] :rhost
          # @option opts [<String, Integer>] :rport
          # @return [Rex::Proto::Kerberos::Client]
          def connect(opts={})
            kerb_client = Rex::Proto::Kerberos::Client.new(
              host: opts[:rhost] || rhost,
              port: (opts[:rport] || rport).to_i,
              timeout: (opts[:timeout] || timeout).to_i,
              context:
                {
                  'Msf'        => framework,
                  'MsfExploit' => self,
                },
              protocol: 'tcp'
            )

            disconnect if client
            self.client = kerb_client

            kerb_client
          end

          # Disconnects the Kerberos client
          #
          # @param kerb_client [Rex::Proto::Kerberos::Client] the client to disconnect
          def disconnect(kerb_client = client)
            kerb_client.close if kerb_client

            if kerb_client == client
              self.client = nil
            end
          end

          # Performs cleanup as necessary, disconnecting the Kerberos client
          # if it's still established.
          def cleanup
            super
            disconnect
          end

          # Sends a kerberos AS request and reads the response
          #
          # @param opts [Hash]
          # @return [Rex::Proto::Kerberos::Model::KdcResponse]
          # @see Msf::Kerberos::Client::AsRequest#build_as_request
          # @see Rex::Proto::Kerberos::Model::KdcResponse
          def send_request_as(opts = {})
            connect(opts)
            req = build_as_request(opts)
            res = client.send_recv(req)
            disconnect
            res
          end

          # Sends a kerberos AS request and reads the response
          #
          # @param opts [Hash]
          # @return [Rex::Proto::Kerberos::Model::KdcResponse]
          # @see Msf::Kerberos::Client::TgsRequest#build_tgs_request
          # @see Rex::Proto::Kerberos::Model::KdcResponse
          def send_request_tgs(opts = {})
            connect(opts)
            req = build_tgs_request(opts)
            res = client.send_recv(req)
            disconnect
            res
          end
        end
      end
    end
  end
end
